package com.nebula.core.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;
import com.nebula.core.constant.Constant;
import com.nebula.core.entity.vo.*;
import com.vesoft.nebula.client.graph.data.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * 数据格式化
 *
 * @Author zhangtao
 * @create 2021/8/20 14:26
 */
@Slf4j
public class NebulaFormat {
    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 允许pojo中有在json串中不存在的字段
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 允许有注释
        objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
    }

    public static List<JobInfo> getJobInfo(ResultSet resultSet) throws UnsupportedEncodingException {
        List<JobInfo> stats = new ArrayList<>();
        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            JobInfo jobInfo = new JobInfo();
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                String fieldName = colNames.get(j);
                if (Constant.JOB_ID.equals(fieldName)) {
                    jobInfo.setJobId(value.asLong());
                } else if (Constant.JOB_STATUS.equals(fieldName)) {
                    jobInfo.setStatus(value.asString());
                }
            }
            stats.add(jobInfo);
        }
        return stats;
    }

    public static List<StatInfo> getStats(ResultSet resultSet) throws UnsupportedEncodingException {
        List<StatInfo> stats = new ArrayList<>();
        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            StatInfo statInfo = new StatInfo();
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                String fieldName = colNames.get(j);
                if (Constant.STAT_NAME.equals(fieldName)) {
                    statInfo.setName(value.asString());
                } else if (Constant.STAT_COUNT.equals(fieldName)) {
                    statInfo.setCount(value.asLong());
                } else if (Constant.STAT_TYPE.equals(fieldName)) {
                    statInfo.setType(value.asString());
                }
            }
            stats.add(statInfo);
        }
        return stats;
    }

    public static List<MetaInfo> getMetaInfo(ResultSet resultSet) throws UnsupportedEncodingException {
        List<MetaInfo> nodes = new ArrayList<>();
        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            MetaInfo metaInfo = new MetaInfo();
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                String fieldName = colNames.get(j);
                String fieldValue = "";
                if (!value.isNull() && !value.isEmpty()) {
                    fieldValue = value.asString();
                }
                if (Constant.META_FIELD.equals(fieldName)) {
                    metaInfo.setField(fieldValue);
                } else if (Constant.META_TYPE.equals(fieldName)) {
                    metaInfo.setType(fieldValue);
                } else if (Constant.META_NULL.equals(fieldName)) {
                    metaInfo.setNullAble(fieldValue);
                } else if (Constant.META_DEFAULT.equals(fieldName)) {
                    metaInfo.setDefaultValue(fieldValue);
                } else if (Constant.META_COMMENT.equals(fieldName)) {
                    metaInfo.setComment(fieldValue);
                }
            }
            nodes.add(metaInfo);
        }
        return nodes;
    }

    public static List<String> getTagsOrEdges(ResultSet resultSet) throws UnsupportedEncodingException {
        List<String> nodes = new ArrayList<>();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                String fieldValue = value.asString();
                nodes.add(fieldValue);
            }
        }
        return nodes;
    }


    public static String getCreateInfo(ResultSet resultSet) throws UnsupportedEncodingException {
        List<String> nodes = new ArrayList<>();
        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                String fieldName = colNames.get(j);
                String fieldValue = "";
                if (!value.isNull() && !value.isEmpty()) {
                    fieldValue = value.asString();
                }
                if (Constant.META_CREATE_EDGE.equals(fieldName)
                        || Constant.META_CREATE_TAG.equals(fieldName)) {
                    nodes.add(fieldValue);
                }
            }
        }
        return CollectionUtils.isEmpty(nodes) ? "" : nodes.get(0);
    }

    public static Map<String, Object> formatResult(ResultSet resultSet) throws UnsupportedEncodingException {
        Map<String, Object> graph = new HashMap<>(8);
        Set<Map<String, Object>> nodes = new HashSet<>();
        List<Map<String, Object>> links = new ArrayList<>();

        graph.put("nodes", nodes);
        graph.put("edges", links);

        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            Map<String, Object> data = new HashMap<>(8);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                if (value.isVertex()) {
                    nodes.add(getNode(value.asNode()));
                } else if (value.isEdge()) {
                    links.add(getLink(value.asRelationship()));
                } else if (value.isPath()) {
                    PathWrapper path = value.asPath();
                    for (PathWrapper.Segment segment : path.getSegments()) {
                        nodes.add(getNode(segment.getStartNode()));
                        nodes.add(getNode(segment.getEndNode()));
                        links.add(getLink(segment.getRelationShip()));
                    }
                } else if (value.isList()) {
                    List<ValueWrapper> list = value.asList();
                    for (ValueWrapper v : list) {
                        if (v.isVertex()) {
                            nodes.add(getNode(v.asNode()));
                        } else if (v.isEdge()) {
                            links.add(getLink(v.asRelationship()));
                        }
                    }
                } else if (value.isMap()) {
                    HashMap<String, ValueWrapper> map = value.asMap();
                    Map<String, Object> nodeData = new HashMap<>(8);
                    for (Map.Entry<String, ValueWrapper> entry : map.entrySet()) {
                        nodeData.put(entry.getKey(), getValue(entry.getValue()));
                    }
                    nodes.add(nodeData);
                } else {
                    data.put(colNames.get(j), getValue(value));
                }
            }
            if (!data.isEmpty()) {
                nodes.add(data);
            }
        }
        return graph;
    }

    public static Map<String, Object> formatResultSets(ResultSet... resultSets) throws UnsupportedEncodingException {
        Map<String, Object> graph = new HashMap<>(8);
        Set<Map<String, Object>> nodes = new HashSet<>();
        List<Map<String, Object>> links = new ArrayList<>();
        graph.put("nodes", nodes);
        graph.put("edges", links);
        for (ResultSet resultSet : resultSets) {
            formatResultSet(resultSet, nodes, links);
        }
        return graph;
    }

    public static <T> List<T> mapResult(ResultSet resultSet, T obj) {
        List<T> result = new ArrayList<>();
        try {
            List<String> colNames = resultSet.keys();
            for (int i = 0; i < resultSet.rowsSize(); i++) {
                ResultSet.Record record = resultSet.rowValues(i);
                Map<String, Object> beanMap = new HashMap<>(8);
                for (int j = 0, length = record.values().size(); j < length; j++) {
                    ValueWrapper value = record.values().get(j);
                    String column = StrUtil.toCamelCase(colNames.get(j));
                    if (value.isVertex()) {
                        beanMap.put(column, getNode(value.asNode()));
                    } else if (value.isEdge()) {
                        beanMap.put(column, getLink(value.asRelationship()));
                    } else if (value.isPath()) {
                        PathWrapper path = value.asPath();
                        List<Object> relationList = new ArrayList<>();
                        for (PathWrapper.Segment segment : path.getSegments()) {
                            relationList.add(getPath(segment));
                        }
                        beanMap.put(column, relationList);
                    } else if (value.isList()) {
                        List<ValueWrapper> list = value.asList();
                        for (ValueWrapper v : list) {
                            if (v.isVertex()) {
                                beanMap.put(column, getNode(v.asNode()));
                            } else if (v.isEdge()) {
                                beanMap.put(column, getLink(v.asRelationship()));
                            }
                        }
                    } else if (value.isMap()) {
                        HashMap<String, ValueWrapper> map = value.asMap();
                        Map<String, Object> nodeData = new HashMap<>(8);
                        for (Map.Entry<String, ValueWrapper> entry : map.entrySet()) {
                            nodeData.put(entry.getKey(), getValue(entry.getValue()));
                        }
                        beanMap.put(column, nodeData);
                    } else {
                        beanMap.put(colNames.get(j), getValue(value));
                    }
                }
                if (colNames.size() == 1) {
                    beanMap.values().stream().forEach(v -> {
                        try {
                            String json = objectMapper.writeValueAsString(v);
                            T t = (T) objectMapper.readValue(json, obj.getClass());
                            result.add(t);
                        } catch (JsonProcessingException e) {
                            log.error("nebula查询对象映射出错", e);
                        }
                    });
                } else {
                    String json = objectMapper.writeValueAsString(beanMap);
                    T t = (T) objectMapper.readValue(json, obj.getClass());
                    result.add(t);
                }
            }
        } catch (Exception e) {
            log.error("nebula查询对象映射出错", e);
        }
        return result;
    }

    private static Map<String, Object> getPath(PathWrapper.Segment segment) throws UnsupportedEncodingException, IllegalAccessException, InstantiationException {
        Map<String, Object> nodeMap = new HashMap<>(8);
        nodeMap.put("src", getNode(segment.getStartNode()));
        nodeMap.put("dst", getNode(segment.getEndNode()));
        nodeMap.put("edge", getLink(segment.getRelationShip()));
        return nodeMap;
    }

    private static void formatResultSet(ResultSet resultSet, Set<Map<String, Object>> nodes, List<Map<String, Object>> links)
            throws UnsupportedEncodingException {
        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            Map<String, Object> data = new HashMap<>(8);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                if (value.isVertex()) {
                    nodes.add(getNode(value.asNode()));
                } else if (value.isEdge()) {
                    links.add(getLink(value.asRelationship()));
                } else if (value.isPath()) {
                    PathWrapper path = value.asPath();
                    for (PathWrapper.Segment segment : path.getSegments()) {
                        nodes.add(getNode(segment.getStartNode()));
                        nodes.add(getNode(segment.getEndNode()));
                        links.add(getLink(segment.getRelationShip()));
                    }
                } else if (value.isList()) {
                    List<ValueWrapper> list = value.asList();
                    for (ValueWrapper v : list) {
                        if (v.isVertex()) {
                            nodes.add(getNode(v.asNode()));
                        } else if (v.isEdge()) {
                            links.add(getLink(v.asRelationship()));
                        }
                    }
                } else {
                    data.put(colNames.get(j), getValue(value));
                }
            }
            if (!data.isEmpty()) {
                nodes.add(data);
            }
        }
    }

    public static <T> List<T> getNodes(ResultSet resultSet, Class<T> clazz) throws UnsupportedEncodingException
            , InstantiationException, IllegalAccessException {
        List<T> nodes = new ArrayList<>();

        List<String> colNames = resultSet.keys();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            Map<String, Object> data = new HashMap<>(8);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                if (value.isVertex()) {
                    nodes.add(getNode(value.asNode(), clazz));
                } else if (value.isList()) {
                    List<ValueWrapper> list = value.asList();
                    for (ValueWrapper v : list) {
                        if (v.isVertex()) {
                            nodes.add(getNode(v.asNode(), clazz));
                        }
                    }
                } else {
                    data.put(colNames.get(j), getValue(value));
                }
            }
            if (!data.isEmpty()) {
                T bean = clazz.newInstance();
                BeanMap beanMap = BeanMap.create(bean);
                beanMap.putAll(data);
                nodes.add(bean);
            }
        }

        return nodes;
    }


    public static ArrayListMultimap<String, Edge> getEdges(ResultSet resultSet) throws UnsupportedEncodingException {
        ArrayListMultimap<String, Edge> edges = ArrayListMultimap.create();
        for (int i = 0; i < resultSet.rowsSize(); i++) {
            ResultSet.Record record = resultSet.rowValues(i);
            for (int j = 0, length = record.values().size(); j < length; j++) {
                ValueWrapper value = record.values().get(j);
                if (value.isEdge()) {
                    Relationship relationship = value.asRelationship();
                    Edge edge = getEdge(relationship);
                    edges.put(relationship.edgeName(), edge);
                } else if (value.isList()) {
                    List<ValueWrapper> list = value.asList();
                    for (ValueWrapper v : list) {
                        if (v.isEdge()) {
                            Relationship relationship = v.asRelationship();
                            Edge edge = getEdge(relationship);
                            edges.put(relationship.edgeName(), edge);
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        return edges;
    }

    private static Edge getEdge(Relationship relationship) throws UnsupportedEncodingException {
        Edge edge = Edge.builder().build();
        edge.setSrcId(relationship.srcId().asString());
        edge.setDstId(relationship.dstId().asString());
        edge.setRank(relationship.ranking());

        Map<String, Object> properties = new HashMap<>(8);
        for (Map.Entry<String, ValueWrapper> entry : relationship.properties().entrySet()) {
            properties.put(entry.getKey(), getValue(entry.getValue()));
        }
        edge.setProperties(properties);
        return edge;
    }

    private static <T> T getNode(Node node, Class<T> clazz)
            throws UnsupportedEncodingException, InstantiationException, IllegalAccessException {
        Map<String, Object> nodeMap = new HashMap<>(8);
        for (String name : node.tagNames()) {
            nodeMap.put("type", name);
            Map<String, ValueWrapper> props = node.properties(name);
            for (Map.Entry<String, ValueWrapper> entry : props.entrySet()) {
                nodeMap.put(entry.getKey(), getValue(entry.getValue()));
            }
        }
        nodeMap.put("id", node.getId().asString());
        T bean = clazz.newInstance();
        BeanMap beanMap = BeanMap.create(bean);
        beanMap.putAll(nodeMap);
        return bean;
    }

    private static Map<String, Object> getNode(Node node)
            throws UnsupportedEncodingException {
        Map<String, Object> nodeMap = new HashMap<>(8);
        for (String name : node.tagNames()) {
            Map<String, ValueWrapper> props = node.properties(name);
            nodeMap.put("type", name);
            for (Map.Entry<String, ValueWrapper> entry : props.entrySet()) {
                if (!entry.getKey().equals("_tag")) {
                    nodeMap.put(StrUtil.toCamelCase(entry.getKey()), getValue(entry.getValue()));
                }
            }
            if (props.containsKey("name")) {
                nodeMap.put("name", props.get("name").asString());
            }
        }
        nodeMap.put("id", getValue(node.getId()));
        nodeMap.put("vid", getValue(node.getId()));
        return nodeMap;
    }

    private static Map<String, Object> getLink(Relationship relationship) throws UnsupportedEncodingException {
        Map<String, Object> link = new HashMap<>(8);
        if (relationship.dstId().isEmpty() || relationship.srcId().isEmpty()) {
            return link;
        }
        link.put(Constant.PATH_SRC, getValue(relationship.srcId()));
        link.put(Constant.PATH_DST, getValue(relationship.dstId()));
        link.put(Constant.PATH_RANK, relationship.ranking());
        link.put(Constant.PATH_EDGE, relationship.edgeName());
        Map<String, ValueWrapper> props = relationship.properties();
        for (Map.Entry<String, ValueWrapper> entry : props.entrySet()) {
            link.put(StrUtil.toCamelCase(entry.getKey()), getValue(entry.getValue()));
        }
        return link;
    }

    public static Object getValue(ValueWrapper value) throws UnsupportedEncodingException {
        if (value.isLong()) {
            return value.asLong();
        } else if (value.isBoolean()) {
            return value.asBoolean();
        } else if (value.isDouble()) {
            return value.asDouble();
        } else if (value.isString()) {
            return value.asString();
        } else if (value.isTime()) {
            TimeWrapper timeWrapper = value.asTime();
            return DateUtil.parse(String.format("%02d:%02d:%02d",
                    timeWrapper.getHour(), timeWrapper.getMinute(), timeWrapper.getSecond()));
        } else if (value.isDate()) {
            DateWrapper dateWrapper = value.asDate();
            return DateUtil.parse(dateWrapper.toString());
        } else if (value.isDateTime()) {
            DateTimeWrapper dateTimeWrapper = value.asDateTime();
            return DateUtil.parse(String.format("%d-%02d-%02d %02d:%02d:%02d +0%d00",
                    dateTimeWrapper.getYear(), dateTimeWrapper.getMonth(), dateTimeWrapper.getDay(),
                    dateTimeWrapper.getHour(), dateTimeWrapper.getMinute(), dateTimeWrapper.getSecond(), dateTimeWrapper.getTimezoneOffset() / 60 / 60),
                    "yyyy-MM-dd HH:mm:ss Z");
        } else if (value.isVertex()) {
            return value.asNode();
        } else if (value.isEdge()) {
            return value.asRelationship();
        } else if (value.isPath()) {
            return value.asPath();
        } else if (value.isMap()) {
            return value.asMap();
        } else if (value.isEmpty() || value.isNull()) {
            return "";
        } else {
            return value.getValue();
        }
    }


    public static Vertex mapToVertex(Map<String, Object> map, String vid, List<String> columns) {
        Vertex vertex = new Vertex();
        vertex.setVid(vid);
        vertex.setProperties(Maps.filterKeys(map, k -> columns.stream().anyMatch(c -> c.equalsIgnoreCase(k))));
        return vertex;
    }

    /**
     * 获取集合中的VID
     *
     * @param nodesMap
     * @return
     */
    public static List<String> getCollectionId(Map<String, Object> nodesMap) {
        List<String> nodes = new ArrayList<>();
        HashSet nodesObject = (HashSet) nodesMap.get("nodes");
        nodesObject.stream().forEach(no -> {
            HashMap no1 = (HashMap) no;
            nodes.add((String) no1.get("id"));
        });
        return nodes;
    }

}
